package cc.rengu.oltp.utility.util;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;

import static javax.crypto.Cipher.getInstance;

/**
 * AES加解密算法工具类
 * Created by 王成 on 2018/5/24.
 */
public class AESUtil {
    /* 字符集编码 */
    private static final String CHARSET = "utf-8";
    /* 密钥算法 */
    private static final String ALGORITHM = "AES";
    /* 加解密算法/工作模式/填充方式 */
    private static final String PKCS5_ECB_MODE_PADDING = "AES/ECB/PKCS5Padding";
    private static final String PKCS5_CBC_MODE_PADDING = "AES/CBC/PKCS5Padding";
    private static final String PKCS7_ECB_MODE_PADDING = "AES/ECB/PKCS7Padding";
    private static final String PKCS7_CBC_MODE_PADDING = "AES/CBC/PKCS7Padding";
    private static final String CBC_NO_PADDING = "AES/CBC/NoPadding";
    private static final String GCM_NO_PADDING = "AES/GCM/NoPadding";

    /* 默认对称解密算法初始向量 iv */
    static byte[] iv = {0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38};

    /**
     * 使用PKCS5Padding算法对数据进行加密
     *
     * @param encryptKey 加密密钥
     * @param data       待加密数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static String pkcs5EcbEncryptData(String encryptKey, String data) throws Exception {
        /* 生成密钥 */
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        keyGenerator.init(128, new SecureRandom(encryptKey.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), ALGORITHM);
        /* 创建密码器 */
        Cipher cipher = getInstance(PKCS5_ECB_MODE_PADDING);
        /* 初始化 */
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return Base64.encodeBase64String(cipher.doFinal(data.getBytes()));
    }

    public static String pkcs5EcbEncryptData(byte[] encryptKey, byte[] data) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(encryptKey, ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_ECB_MODE_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        return Base64.encodeBase64String(cipher.doFinal(data));
    }

    /**
     * 使用pkcs5Padding算法对数据进行解密
     *
     * @param decryptKey 解密密钥
     * @param data       Base64加密的数据
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static String pkcs5EcbDecryptData(String decryptKey, String data) throws Exception {
        /* 生成密钥 */
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        keyGenerator.init(128, new SecureRandom(decryptKey.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), ALGORITHM);
        /* 创建密码器 */
        Cipher cipher = Cipher.getInstance(PKCS5_ECB_MODE_PADDING);
        /* 初始化 */
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return new String(cipher.doFinal(Base64.decodeBase64(data)));
    }

    public static String pkcs5EcbDecryptData(byte[] decryptKey, byte[] data) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(decryptKey, ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_ECB_MODE_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        return new String(cipher.doFinal(data));
    }

    /**
     * 使用PKCS7密钥算法对数据进行加密
     *
     * @param encryptKey 加密KEY
     * @param content    加密数据源
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static String pkcs7EcbEncryptData(String encryptKey, String content) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(encryptKey.getBytes());
        SecureRandom securerandom = new SecureRandom(messageDigest.digest());
        keyGenerator.init(256, securerandom);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, ALGORITHM);
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(PKCS7_ECB_MODE_PADDING, "BC");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] byteContent = content.getBytes(CHARSET);
        byte[] cryptograph = cipher.doFinal(byteContent);
        return Base64.encodeBase64String(cryptograph);
    }

    /**
     * 使用PKCS7算法对数据进行解密
     *
     * @param decryptKey 解密密钥
     * @param content    需要解密的数据
     * @return 解密后的数据
     * @throws Exception 异常
     */
    public static String pkcs7EcbDecryptData(String decryptKey, String content) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(decryptKey.getBytes());
        SecureRandom securerandom = new SecureRandom(messageDigest.digest());
        keyGenerator.init(256, securerandom);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, ALGORITHM);
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(PKCS7_ECB_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return new String(cipher.doFinal(Base64.decodeBase64(content.getBytes())));
    }


    /**
     * 使用指定的初始化向量对数据进行加密
     *
     * @param encryptKey 加密密钥
     * @param content    需要加密的数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static byte[] pkcs5CbcEncryptData(byte[] encryptKey, byte[] content) throws Exception {
        /* 如果密钥不足16位 */
        if (encryptKey.length % 16 != 0) {
            int groups = encryptKey.length / 16 + (encryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(encryptKey, 0, temp, 0, encryptKey.length);
            encryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        Key key = new SecretKeySpec(Base64.decodeBase64(encryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_CBC_MODE_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(initIv(PKCS5_CBC_MODE_PADDING)));
        byte[] encryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(encryptBytes);
    }

    /**
     * 使用指定的初始化向量加密
     *
     * @param encryptKey 加密密钥
     * @param content    需要加密的数据
     * @param sIv        初始化向量
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static byte[] pkcs5CbcEncryptData(byte[] encryptKey, byte[] content, byte[] sIv) throws Exception {
        /* 如果密钥不足16位 */
        if (encryptKey.length % 16 != 0) {
            int groups = encryptKey.length / 16 + (encryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(encryptKey, 0, temp, 0, encryptKey.length);
            encryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        Key key = new SecretKeySpec(Base64.decodeBase64(encryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_CBC_MODE_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(sIv));
        byte[] encryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(encryptBytes);
    }

    /**
     * 使用指定的初始化向量解密
     *
     * @param decryptKey 解密密钥
     * @param content    需要解密的数据
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static byte[] pkcs5CbcDecryptData(byte[] decryptKey, byte[] content) throws Exception {
        /* 如果密钥不足16位 */
        if (decryptKey.length % 16 != 0) {
            int groups = decryptKey.length / 16 + (decryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(decryptKey, 0, temp, 0, decryptKey.length);
            decryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(Base64.decodeBase64(decryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_CBC_MODE_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initIv(PKCS5_CBC_MODE_PADDING)));
        byte[] decryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(decryptBytes);
    }

    /**
     * 使用指定的初始化向量解密
     *
     * @param decryptKey 解密密钥
     * @param content    需要解密的数据
     * @param sIv        初始化向量
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static byte[] pkcs5CbcDecryptData(byte[] decryptKey, byte[] content, byte[] sIv) throws Exception {
        /* 如果密钥不足16位 */
        if (decryptKey.length % 16 != 0) {
            int groups = decryptKey.length / 16 + (decryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(decryptKey, 0, temp, 0, decryptKey.length);
            decryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(Base64.decodeBase64(decryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS5_CBC_MODE_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(sIv));
        byte[] decryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(decryptBytes);
    }

    /**
     * 使用指定的初始化向量对数据进行加密
     *
     * @param encryptKey 加密密钥
     * @param content    需要加密的数据
     * @param sIv        初始化向量
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static byte[] pkcs7CbcEncryptData(byte[] encryptKey, byte[] content, byte[] sIv) throws Exception {
        /* 如果密钥不足16位 */
        if (encryptKey.length % 16 != 0) {
            int groups = encryptKey.length / 16 + (encryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(encryptKey, 0, temp, 0, encryptKey.length);
            encryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(Base64.decodeBase64(encryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS7_CBC_MODE_PADDING, "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(sIv));
        byte[] encryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(encryptBytes);
    }

    /**
     * 使用默认的向量对数据进行加密
     *
     * @param encryptKey 加密密钥
     * @param content    需要加密的数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static String pkcs7CbcEncryptData(byte[] encryptKey, byte[] content) throws Exception {
        /* 如果密钥不足16位 */
        if (encryptKey.length % 16 != 0) {
            int groups = encryptKey.length / 16 + (encryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(encryptKey, 0, temp, 0, encryptKey.length);
            encryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(encryptKey, ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS7_CBC_MODE_PADDING, "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(initIv(PKCS7_CBC_MODE_PADDING)));
        byte[] encryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64String(encryptBytes);
    }

    /**
     * 使用指定的初始化向量解密
     *
     * @param decryptKey 解密密钥
     * @param content    需要解密的数据
     * @param sIv        初始化向量
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static byte[] pkcs7CbcDecryptData(byte[] decryptKey, byte[] content, byte[] sIv) throws Exception {
        /* 如果密钥不足16位 */
        if (decryptKey.length % 16 != 0) {
            int groups = decryptKey.length / 16 + (decryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(decryptKey, 0, temp, 0, decryptKey.length);
            decryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(Base64.decodeBase64(decryptKey), ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS7_CBC_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(sIv));
        byte[] decryptBytes = cipher.doFinal(content);
        return Base64.encodeBase64(decryptBytes);
    }

    /**
     * 使用默认的初始化向量解密
     *
     * @param decryptKey 解密密钥
     * @param content    需要解密的数据
     * @return 解密后的明文
     * @throws Exception 异常
     */
    public static String pkcs7CbcDecryptData(byte[] decryptKey, byte[] content) throws Exception {
        /* 如果密钥不足16位 */
        if (decryptKey.length % 16 != 0) {
            int groups = decryptKey.length / 16 + (decryptKey.length % 16 != 0 ? 1 : 0);
            byte[] temp = new byte[groups * 16];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(decryptKey, 0, temp, 0, decryptKey.length);
            decryptKey = temp;
        }
        /* 初始化 */
        Security.addProvider(new BouncyCastleProvider());
        /* 转化成JAVA的密钥格式 */
        Key key = new SecretKeySpec(decryptKey, ALGORITHM);
        Cipher cipher = Cipher.getInstance(PKCS7_CBC_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initIv(PKCS7_CBC_MODE_PADDING)));
        byte[] decryptBytes = cipher.doFinal(content);
        return new String(decryptBytes);
    }

    public static String decryptData(String key, String content) throws Exception {
        Cipher cipher = Cipher.getInstance(PKCS5_ECB_MODE_PADDING);
        SecretKeySpec deskey = new SecretKeySpec(key.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, deskey);
        return new String(cipher.doFinal(Base64.decodeBase64(content)));
    }

    /**
     * 初始化向量
     *
     * @param algorithmType 算法类型
     * @return 初始化向量值
     */
    private static byte[] initIv(String algorithmType) {
        try {
            Cipher cipher = Cipher.getInstance(algorithmType);
            int blockSize = cipher.getBlockSize();
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        } catch (Exception e) {
            int blockSize = 16;
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        }
    }

    /**
     * 随机生成AES对称密钥
     *
     * @param length 密钥长度
     * @return
     * @throws Exception
     */
    public static String generateAesKey(int length) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance(ALGORITHM);
        kgen.init(length);
        SecretKey skey = kgen.generateKey();
        return Base64.encodeBase64String(skey.getEncoded());
    }

    /**
     * NoPadding加密
     *
     * @param encryptKey 加密密钥
     * @param content    加密数据
     * @return 加密后的字符串
     * @throws Exception 异常
     */
    public static String cbcNoPaddingEncryptData(byte[] encryptKey, byte[] content) throws Exception {
        Cipher cipher = Cipher.getInstance(CBC_NO_PADDING);
        SecretKeySpec securekey = new SecretKeySpec(encryptKey, ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, securekey, iv);
        return new String(cipher.doFinal(content));
    }

    /**
     * NoPadding解密
     *
     * @param decryptKey 解密密钥
     * @param content    加密数据
     * @return 解密后的数据
     * @throws Exception 异常
     */
    public static String cbcNoPaddingDecryptData(byte[] decryptKey, byte[] content) throws Exception {
        Cipher cipher = Cipher.getInstance(CBC_NO_PADDING);
        SecretKeySpec securekey = new SecretKeySpec(decryptKey, ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".getBytes());
        cipher.init(Cipher.DECRYPT_MODE, securekey, iv);
        byte[] decryptBytes = cipher.doFinal(content);
        return new String(decryptBytes, StandardCharsets.UTF_8);
    }

    public static String gcmDecryptToString(byte[] decryptKey, byte[] associatedData, byte[] nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance(GCM_NO_PADDING);
            SecretKeySpec key = new SecretKeySpec(decryptKey, ALGORITHM);
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(java.util.Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
